用PHP撰寫網頁時,該注意哪些寫法,避免安全性產生問題?
首先要找出前端網頁中允許使用者輸入的部分,像是表單標籤<FORM>內允許輸入的欄位或選單部分,這些允許使用者輸入的部分,表示使用者可以自行決定要輸入的,正常的內容當然是最好,但也可能是超出允許範圍之外的內容,甚至是帶有惡意的程式碼,這通常也是嘗試入侵時會被利用的點。一般常見的例子像是使用者登入時的帳號密碼欄位等。此外,許多動態產生的連結URL也是容易被利用的輸入點,例如:http://www.example.com/query.php?qid=13313&uid=31331&page=2其中qid、uid、page都是有機會讓使用者修改輸入的輸入點,而在PHP中會以$_REQUEST、$_POST、$_GET等參數傳入網站應用系統,後續再處理與應用。
設變數接受輸入
一般來說,程式要使用外部輸入的資料時,要避免直接使用$_REQUEST、$_POST、$_GET這些原始輸入,較安全的方法通常是先設個變數接收,然後依據變數的用途,執行必要的檢查與過濾後再使用,如此一來,只要程式設計師養成好習慣,例如︰「不使用原始輸入資料」、「輸入資料過濾檢查後再使用」,效果就像在網路上部署防火牆一樣,能將外部資料與內部的資料處理流程區隔開來,並檢查與過濾進入內部的所有資料,符合條件者才准傳入。這是建立「輸入驗證」防線的基礎原則。
限制輸入環境
輸入環境的限制是一種避免出現非預期輸入的方法。如果有明確的輸入選項,就不要提供文字輸入欄位,例如性別在絕大部分情況下,只有「男」與「女」兩個選擇,就不要提供使用者有輸入「石頭」的機會;一年不會超過12個月,每個月都有固定的天數,這些最好都做成選單讓使用者選擇。
就算不是選項型態,多半也有固定的格式,也因此會有固定的條件限制,例如信用卡號碼為16碼的數字,就不會有英文、數字混雜的情況;像身份證字號本身有特定的編碼方式,也可以使用公式驗證;電子郵件帳號一定要「@」,這些都是很實際的例子。
利用白名單選項,也是一種做法。限制輸入的可能性,或限制輸入的格式、型態、長度、內容等,只要條件越明確,就越能避免有非預期的輸入。通常網頁程式設計師會利用JavaScript與正規表示式(Regular Expression)預先檢查輸入的內容,不過,JavaScript的限制功能多半很容易就被繞過或是關閉,只能「防君子不防小人」,還是要在伺服器端對這些輸入再驗證一次,比較保險。
在輸入過濾檢查工作前,仍需要先瞭解資料要輸入的環境,例如會在哪些環境下使用,這樣才知道後面需要設定什麼過濾檢查的條件。舉例來說,如果我們知道變數$var將要傳入SQL語法中當作關鍵字查詢,我們才會知道除了要檢查與過濾$var內容的合理性之外,還需要考慮$var在SQL語法中的限制,這樣對$var的過濾檢查才會完整;如果$var會傳進system(),作為Linux執行指令的一部份,就要考慮$var會對Linux指令造成的影響而預先過濾,這時的過濾條件又跟之前用在SQL語法時是不同的。
防止SQL Injection大部分開發者都會應用 addslashes() 來防止SQL Injection的發生,但addslashes()只會過濾單引號(')、雙引號(")、反斜線 backslash(\) 以及空字元 NUL((the null byte,特殊攻擊仍有辦法跳過檢查。而php.ini中的magic_quotes_gpc選項雖然能夠在$_GET,$_POST,$_COOKIE這三種輸入執行過濾,但因為動作與addslashes()雷同,如前所述,一樣有被跳脫的疑慮。
所以,我們建議採用資料庫提供的過濾函式,譬如MySQL可以使用mysql_real_escape_string()濾除跳脫字元,也免除了針對每個輸入都加上過濾函式的繁瑣工作。
如果PHP程式中有呼叫系統指令,則可以使用escapeshellcmd()過濾Shell內的一些有意義的特殊字元,或是乾脆使用escapeshellarg(),將所有可能的輸入參數都濾除,這樣可以防止Command Injection的發生。
當輸入資料的用途是指出系統內的某個或某些檔案時,例如應用在require()、require_once()等,應該限制僅允許輸入檔名,將目錄寫死或設定絕對路徑,也可以將「../」這樣的目錄跳脫濾除,來避免Code Injection的問題。
輸出、輸入都要檢查,防XSS
內容輸出前,也需要進行過濾檢查,避免插入了非預期的網頁內容、修改網頁呈現,甚至插入惡意的JavaScript,也就是Cross-Site Scripting(XSS)。在PHP中可以使用htmlspecialchars()將 & > " < 轉成HTML字串格式,例如:
* & (和) 轉成 &
* " (雙引號) 轉成 "
* < (小於) 轉成 <
* > (大於) 轉成 >
這樣可以避免使用者的輸入中當有<IFRAME>、<SCRIPT>這些HTML標籤時會被瀏覽器當成網頁內容而執行。如果資料輸入時本就不允許輸入HTML標籤,不如直接在輸入檢查時,以strip_tag()直接濾掉,免除後患。
總而言之,完善的輸入驗證(Input Validation)可以預防大部分的網頁資安問題,而完善的輸出驗證(Output Validation)則可以防止OWASP 2007 Top 10排名第一的XSS攻擊。做好這些輸出入驗證工作,便可以有效強化PHP網頁程式的安全性。